# Copyright (c) 2011 Peter Shinners

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.



import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import unittest
from minivec import Vec
import minivec


class TestVector(unittest.TestCase):
    def setUp(self):
        minivec.ResetError()
        minivec.ResetEpsilon()


    def TestConstructors(self):
        "Various forms of constructors"
        zeros = Vec(0.0)
        ones = Vec(1.0)
        def counter():
            yield 1
            yield 2
            yield 3
        numbers = {1:"one", 2:"two", 3:"three"}
        vectors = [
            Vec(),  # 0
            zeros,
            ones,  # 2
            Vec(1.1, 2.2, 3.3),  # 3
            Vec((1.1, 2.2, 3.3)),  # 4
            Vec([1, 1, 1]),  # 5
            Vec(ones),  # 6
            Vec(counter()),  # 7
            Vec(numbers)  # 8
        ]
        tuples = map(tuple, vectors)

        self.assertEqual(tuples[0], tuples[1])
        self.assertEqual(tuples[3], tuples[4])
        self.assertEqual(tuples[5], tuples[6])
        self.assertEqual(tuples[7], tuples[8])
        self.assertEqual(type(tuples[5][0]), float)

        
    def TestBadConstructors(self):
        "Constructors that should fail"
        self.assertRaises(TypeError, lambda: Vec("vector"))
        self.assertRaises(TypeError, lambda: Vec("1.0"))
        self.assertRaises(TypeError, lambda: Vec((1, 2, 3, 4)))
        self.assertRaises(TypeError, lambda: Vec((1, 2)))
        self.assertRaises(TypeError, lambda: Vec(1, 2, 3, 4))
        self.assertRaises(TypeError, lambda: Vec(1, 2))
        self.assertRaises(TypeError, lambda: Vec("one", "two", "three"))
        self.assertRaises(TypeError, lambda: Vec(("one", "two", "three")))
        self.assertRaises(TypeError, lambda: Vec(1j))
        self.assertRaises(TypeError, lambda: Vec(1, 2j, 3))
        self.assertRaises(TypeError, lambda: Vec((1, 2, 3j)))


    def TestNonzero(self):
        "Truthness of vectors"
        self.assertFalse(Vec())
        self.assertFalse(Vec(0))
        self.assertFalse(Vec([0.0, 0.0, 0.0]))
        self.assertTrue(Vec(1.0))
        self.assertTrue(Vec(0.1, 0.0, 0))
        self.assertTrue(Vec(0, -0.1, 0))


    def TestEquality(self):
        "Equality and inequality"
        self.assertEqual(Vec(), Vec(0))
        self.assertEqual(Vec(1), (1.0, 1, 1.0))
        self.assertEqual(Vec(2), Vec(2))

        # Ensure hashing works for sets and dictionaries
        s = set((Vec(), Vec(0.0), Vec(.1, .2, .3)))
        self.assertEqual(len(s), 2)
        self.assertTrue(Vec(0, 0, 0) in s)
        self.assertTrue(Vec(.1, .2, .3) in s)
        self.assertFalse(Vec(1, 2, 3) in s)
        s.add(Vec(11, 12, 13))
        s.difference_update([Vec(0, 0, 0), Vec(1, 1, 1)])
        self.assertEqual(len(s), 2)

        h = Vec(0.3, 3.7, 477.1)
        i = h + 1e-12
        self.assertNotEqual(h, i)
        from math import fmod
        epsilon = minivec.Epsilon()
        phx = h[0] - fmod(h[0], epsilon)
        phy = h[1] - fmod(h[1], epsilon)
        phz = h[2] - fmod(h[2], epsilon)
        pix = i[0] - fmod(i[0], epsilon)
        piy = i[1] - fmod(i[1], epsilon)
        piz = i[2] - fmod(i[2], epsilon)

        hh = hash(h)
        hi = hash(i)
        self.assertNotEqual(hash(h), hash(i))

        a = Vec(-1,0,1)
        d = {a: True, Vec(1,0,-1): True}
        self.assertTrue(a in d)
        self.assertTrue(Vec(1, 0, -1) in d)
        self.assertFalse(Vec(1, 2, 3) in d)
        d[Vec(-1, 0, 1)] = False
        self.assertEqual(len(d), 2)

        # Cannot compare with scalars
        self.assertRaises(TypeError, lambda: a == 4)
        self.assertRaises(TypeError, lambda: a > 2)

        # Test sloppy epsilon equality
        self.assertNotEqual(a, a + 1e-12)
        self.assertNotEqual(a, a - 1e-12)
        self.assertNotEqual(a, a + 1e-2)
        self.assertNotEqual(a, a - 1e-2)


    def testAlmost(self):
        """Test equality with epsilon"""
        epsilon = minivec.Epsilon()
        self.assertFalse(Vec(0.0).almost(epsilon))
        self.assertFalse(Vec(0.0).almost(-epsilon))

        a = Vec(5.0)

        self.assertTrue(a.almost(a))
        self.assertTrue(a.almost(5.0))
        self.assertTrue(a.almost(a + 1e-12))
        self.assertTrue(a.almost(5.0 + 1e-12))
        self.assertTrue(a.almost(a - 1e-12))
        self.assertTrue(a.almost((5.0, 5.0, 5.0)))
        self.assertTrue(a.almost(Vec(5)))
        self.assertFalse(a.almost(-a))

        self.assertFalse(a.almost(a + 1e-2))
        self.assertFalse(a.almost(5.0 + 1e-2))
        self.assertFalse(a.almost(a - 1e-2))

        b = Vec(12.0)
        minivec.PushEpsilon(1.0)
        self.assertTrue(b.almost(12.8, 12, 12))
        self.assertFalse(b.almost(13.4, 12, 12))

        
    def TestExact(self):
        "Test equality without epsilon"
        a = Vec(5.0)

        self.assertTrue(a.exact(a))
        self.assertTrue(a.exact(5.0))
        self.assertFalse(a.exact(a + 1e-12))
        self.assertFalse(a.exact(5.0 + 1e-12))
        self.assertFalse(a.exact(a - 1e-12))
        self.assertTrue(a.exact((5.0, 5.0, 5.0)))
        self.assertTrue(a.exact(Vec(5)))
        self.assertFalse(a.exact(-a))


    def TestSequence(self):
        "Sequence style access"
        a = Vec(1, 2, 3)

        self.assertEqual(a[0], 1.0)
        self.assertEqual(a[1], 2.0)
        self.assertEqual(a[2], 3.0)
        self.assertEqual(a[-1], 3.0)
        self.assertEqual(a[-2], 2.0)
        self.assertEqual(a[-3], 1.0)
        self.assertEqual([v for v in a], [1.0, 2.0, 3.0])
        self.assertEqual(max(a), 3.0)
        self.assertEqual(any(a), True)
        self.assertEqual(a[:2], (1.0, 2.0))
        self.assertEqual(a[1:], (2.0, 3.0))
        self.assertEqual(a[3:], ())


    def TestElementwiseOperators(self):
        "Operators with element wise values"
        zeros = Vec()
        ones = Vec(1.0)
        a = Vec(1.1, 2.2, 3.3)
        b = Vec(10)
        c = (-5, 12, 3)

        self.assertEqual(a + b, (11.1, 12.2, 13.3))
        self.assertEqual(a - a, b - b)
        self.assertEqual(a * b, (11, 22, 33))
        self.assertEqual(a / zeros, (0, 0, 0))
        self.assertEqual(a * ones, a)
        self.assertEqual(b * ones, b)
        self.assertEqual(ones * ones, ones)
        self.assertEqual(b // a, (9, 4, 3))
        self.assertEqual(a ** zeros, Vec(1))
        self.assertEqual(a ** a // 1, (1, 5, 51))
        self.assertEqual((a * c), (a[0] * c[0], a[1] * c[1], a[2] * c[2]))
        self.assertEqual(c * ones, c)
        self.assertEqual(ones * c, c)
        self.assertEqual(c - zeros, c)


    def TestScalarOperators(self):
        "Operators with scalars"
        zeros = Vec()
        ones = Vec(1.0)
        a = Vec(1.1, 2.2, 3.3)

        self.assertEqual(a + 10, (11.1, 12.2, 13.3))
        self.assertEqual(a * 10, (11, 22, 33))
        self.assertEqual(a / 0, (0, 0, 0))
        self.assertEqual(a * 1, Vec(a))
        self.assertEqual(2 * a, Vec(a + a))
        self.assertEqual(a ** 0.0, Vec(1))
        self.assertEqual(a * 55.5, (a[0] * 55.5, a[1] * 55.5, a[2] * 55.5))


    def TestLength(self):
        "Length and normalization"
        a = Vec(1, 0, 0)
        b = Vec(0, 1, 0)
        c = Vec(0, 0, -1)
        d = Vec(1, 1, -1)
        e = Vec()

        self.assertEqual(a.length(), 1.0)
        self.assertEqual(b.length(), 1.0)
        self.assertEqual(c.length(), 1.0)
        self.assertAlmostEqual(d.length(), 1.7320508)
        self.assertAlmostEqual(d.length2(), d.length() ** 2.0)
        self.assertEqual(e.length(), 0.0)
        self.assertEqual(e.length2(), 0.0)

        self.assertEqual(d.normalize().length(), 1.0)
        self.assertEqual(d.normalize(2.2).length(), 2.2)
        self.assertAlmostEqual(d.normalize(-2.2).length2(), 4.84)

        self.assertEqual(e.normalize(), e)
        self.assertEqual(e.normalize(1.5), e)


    def TestCrossDot(self):
        "Cross and dot operations"
        x = Vec(1, 0, 0)
        y = Vec(0, 1, 0)
        z = Vec(0, 0, 1)
        a = Vec(.2, .95, -.2)
        b = Vec(.7, -.6, .3)

        self.assertEqual(x.cross(y), z)
        self.assertEqual(y % x, -z)
        self.assertEqual(x.dot(y), 0.0)
        self.assertEqual(x.angle(y), 90.0)
        self.assertEqual(y ^ z, 0.0)
        self.assertEqual(x.cross(y).dot(x), 0.0)
        self.assertEqual(x.cross(y).angle(x), 90.0)
        self.assertAlmostEqual(a.dot(b), -.49)
        self.assertAlmostEqual(a.angle(b), 120.65567245)
        self.assertAlmostEqual(a.angle(b * 5.5), 120.65567245)
        self.assertAlmostVec(a.cross(b), (0.165, -0.2, -0.785))

        
    def TestSubclass(self):
        "Ensure the subclass is preserved"
        class SuperVec3(Vec):
            pass

        a = SuperVec3(1, 2, 3)
        b = SuperVec3(12)
        c = SuperVec3()
        self.assertTrue(isinstance(b.normalize(), SuperVec3))
        self.assertTrue(isinstance(a + b, SuperVec3))
        self.assertTrue(isinstance(a.cross(b), SuperVec3))


    def TestReflect(self):
        a = Vec(0, 1, 0)
        b = Vec(.3, -0.5, 0)
        self.assertEqual(b.reflect(a), (0.3, 0.5, 0))


    def TestProject(self):
        a = Vec(0, 1, 0)
        b = Vec(.3, -0.5, 0)
        c = Vec(.3, 0.5, 0)
        self.assertEqual(b.project(a), (0, -0.5, 0))
        self.assertEqual(c.project(a), (0, 0.5, 0))


    def TestOrthoganal(self):
        a = Vec(0, 1, 0)
        b = Vec(.3, -0.5, 0)
        # No idea what this is supposed to be, this is what first version generated
        self.assertAlmostVec(a.orthogonal(b), (0.7411764706, -1.2352941176, 0.0))
        self.assertAlmostVec(b.orthogonal(a), (0.0, 1.5, 0))


    def TestDistance(self):
        a = Vec(1.1, 2.2, 3.3)
        b = Vec(10)
        c = Vec(-5, 12, 3)

        self.assertEqual(a.length(c), c.length(a))
        self.assertAlmostEqual(b.length2(c), c.length(b) ** 2.0)
        self.assertAlmostEqual(a.length(b), 13.599264686)

        
    def TestLerp(self):
        a = Vec(1,2,3)
        b = Vec(2,3,4)

        self.assertEqual(a.lerp(0.5, b), (1.5, 2.5, 3.5))
        self.assertEqual(a.lerp(0.3, b), b.lerp(0.7, a))
        self.assertEqual(a.lerp(0.0, b), a)
        self.assertEqual(a.lerp(1.0, b), b)
        self.assertEqual(a.lerp(2.0, b), (3.0, 4.0, 5.0))
        self.assertEqual(a.lerp(-.5, b), (0.5, 1.5, 2.5))


    def TestSwizzle(self):
        a = Vec(2,3,4)
        self.assertEqual(a.swizzle("01!!"), [0.0, 1.0, -1.0, -1.0])
        self.assertEqual(a.swizzle("zyx"), [4.0, 3.0, 2.0])
        self.assertEqual(a.swizzle("Y"), [-3.0])
        self.assertEqual(a.swizzle("XZ0"), [-2.0, -4.0, 0.0])
        self.assertEqual(a.swizzle(""), [])
        self.assertRaises(KeyError, lambda: a.swizzle("yagni"))


    def assertAlmostVec(self, first, second):
        if not first.almost(second):
            raise self.failureException, \
                  ('%r !~= %r' % (first, second))


if __name__ == "__main__":
    unittest.main()
